//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using LitwareHR.Portfolio.Gateways;
using System.Workflow.Activities.Rules;
using System.CodeDom;
using System.Workflow.ComponentModel.Serialization;
using System.IO;
using System.Xml;
using Shp.Runtime.Contract;

namespace LitwareHR.PrivateApp.Presenters.ConfigCusto.Process
{
    public class RulesetPresenter
    {
        IRulesetView _view;

        public RulesetPresenter(IRulesetView view)
        {
            _view = view;
        }

        public void OnViewLoaded()
        {
            string tmp = WorkflowGateway.GetRuleSetDef();
            WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
            using (StringReader strReader = new StringReader(tmp))
            {
                using (XmlReader xmlReader = XmlReader.Create(strReader))
                {
                    RuleSet ruleSet = serializer.Deserialize(xmlReader) as RuleSet;
                    _view.Ruleset = GetWorkflowRules(ruleSet);
                    _view.ShowRuleset();
                }
            }
        }

        private List<WorkflowRule> GetWorkflowRules(RuleSet ruleset)
        {
            List<WorkflowRule> rules = new List<WorkflowRule>();
            foreach (Rule rule in ruleset.Rules)
            {
                rules.Add(BuildWorkflowRule(rule));                
            }
            return rules;
        }

        #region WorkflowRule Build

        private WorkflowRule BuildWorkflowRule(Rule rule)
        {
            WorkflowRule workflowRule = new WorkflowRule();

            GetTargetWorkflowIdFromRule(rule, workflowRule);

            RuleExpressionCondition cond = (RuleExpressionCondition)rule.Condition;
            CodeBinaryOperatorExpression expression = (CodeBinaryOperatorExpression)cond.Expression;
            CodeMethodInvokeExpression methodExpression;
            CodePrimitiveExpression valueExpression;
            if (expression.Left.GetType() == typeof(CodeMethodInvokeExpression))
            {
                workflowRule.Operation = Constants.Ruleset.OperandEqual;
                methodExpression = (CodeMethodInvokeExpression)expression.Left;
                valueExpression = (CodePrimitiveExpression)expression.Right;
            }
            else
            {
                workflowRule.Operation = Constants.Ruleset.OperandNotEqual;
                methodExpression = (CodeMethodInvokeExpression)((CodeBinaryOperatorExpression)expression.Left).Left;
                valueExpression = (CodePrimitiveExpression)((CodeBinaryOperatorExpression)expression.Left).Right;
            }

            if (methodExpression.Parameters.Count > 0)
            {
                workflowRule.Property = ((CodePrimitiveExpression)methodExpression.Parameters[0]).Value.ToString();
            }

            workflowRule.Value = valueExpression.Value.ToString();

            return workflowRule;
        }

        private static void GetTargetWorkflowIdFromRule(Rule rule, WorkflowRule workflowRule)
        {
            if (rule.ThenActions.Count > 0)
            {
                RuleStatementAction action = (RuleStatementAction)rule.ThenActions[0];
                CodeAssignStatement stmt = (CodeAssignStatement)action.CodeDomStatement;
                workflowRule.TargetWorkflowId = (Guid)((CodePrimitiveExpression)stmt.Right).Value;
            }
        }

        #endregion

        public void OnAddRule()
        {
            _view.UpdateRuleset();
            _view.Ruleset.Add(new WorkflowRule());
            _view.ShowRuleset();
        }

        public void OnRemoveRule(int index)
        {
            _view.Ruleset.RemoveAt(index);
            _view.ShowRuleset();
        }

        public void OnMoveRuleUp(int index)
        {
            WorkflowRule Rule = _view.Ruleset[index];
            if (Rule != null && index > 0)
            {
                _view.Ruleset.RemoveAt(index);
                _view.Ruleset.Insert(index - 1, Rule);
            }
            _view.ShowRuleset();
        }

        public void OnMoveRuleDown(int index)
        {
            WorkflowRule Rule = _view.Ruleset[index];
            if (Rule != null && index < _view.Ruleset.Count - 1)
            {
                _view.Ruleset.RemoveAt(index);
                _view.Ruleset.Insert(index + 1, Rule);
            }
            _view.ShowRuleset();
        }

        public void OnSaveWorkflow()
        {
            _view.UpdateRuleset();
            RuleSet ruleset = BuildRuleSet(_view.Ruleset);

            StringWriter writer = new StringWriter();
            XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
            xmlWriterSettings.OmitXmlDeclaration = true;
            XmlWriter xmlWriter = XmlWriter.Create(writer, xmlWriterSettings);
            
            WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();

            serializer.Serialize(xmlWriter, ruleset);
            string rulesetMarkup = writer.ToString();
            WorkflowGateway.UpdateRuleSetDef(rulesetMarkup);
        }

        #region RuleSet Build

        private RuleSet BuildRuleSet(List<WorkflowRule> workflowRuleset)
        {
            RuleSet ruleSet = new RuleSet();
            ruleSet.ChainingBehavior = RuleChainingBehavior.Full;
            ruleSet.Name = "RecruitingRules";
            ruleSet.Description = "RecruitingRules";

            int i = 0;
            foreach (WorkflowRule workflowRule in workflowRuleset)
            {
                Rule rule = BuildRule(workflowRule);
                rule.Name = "Rule " + i.ToString();
                ruleSet.Rules.Add(rule);
                i++;
            }
            return ruleSet;
        }

        private Rule BuildRule(WorkflowRule workflowRule)
        {
            Rule rule = new Rule();
            rule.ReevaluationBehavior = RuleReevaluationBehavior.Always;
            rule.Priority = 0;
            rule.Active = true;

            rule.Condition = BuildCondition(workflowRule);
            rule.ThenActions.Add(BuildAction(workflowRule));

            return rule;
        }

        private RuleCondition BuildCondition(WorkflowRule rule)
        {
            CodePropertyReferenceExpression posDataRef = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "Position");
            CodeMethodReferenceExpression getPropertyValueMethod = new CodeMethodReferenceExpression(posDataRef, "GetFieldValue");
            CodePrimitiveExpression parameter = new CodePrimitiveExpression(rule.Property);
            CodeMethodInvokeExpression left = new CodeMethodInvokeExpression(getPropertyValueMethod, parameter);

            CodeExpression right = new CodePrimitiveExpression(rule.Value);
            CodeExpression expression = new CodeBinaryOperatorExpression(left, CodeBinaryOperatorType.ValueEquality, right);

            switch (rule.Operation)
            {
                case Constants.Ruleset.OperandNotEqual:
                    expression = new CodeBinaryOperatorExpression(expression, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(false));
                    break;
            }

            RuleCondition cond = new RuleExpressionCondition(expression);
            return cond;
        }

        private RuleAction BuildAction(WorkflowRule rule)
        {
            CodeExpression left = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "TargetEvaluationProcess");
            CodeExpression right = new CodePrimitiveExpression(rule.TargetWorkflowId);

            CodeAssignStatement stmt = new CodeAssignStatement(left, right);
            RuleStatementAction action = new RuleStatementAction(stmt);

            return action;
        }

        #endregion

        public string[] GetEntityProperties(string tenantAlias, string entityDefName)
        {
            EntityField[] fields = DatamodelGateway.GetEntityDefFields(tenantAlias, entityDefName);
            List<string> list = new List<string>();
            foreach (EntityField field in fields)
            {
                list.Add(field.Name);
            }
            return list.ToArray();
        }

        public Dictionary<int, string> Operations
        {
            get
            {
                Dictionary<int, string> operations = new Dictionary<int, string>();
                operations.Add(Constants.Ruleset.OperandEqual, "equals");
                operations.Add(Constants.Ruleset.OperandNotEqual, "not equals");
                return operations;
            }
        }

        public Dictionary<Guid, string> Workflows
        {
            get
            {
                return WorkflowGateway.GetWorkflowDefList();
            }
        }
    }
}
